name: Build and Release W&B SDK Core

on:
  workflow_dispatch:
    inputs:
      use_pre:
        type: boolean
        description: "pre-release"
        required: false
        default: false

jobs:
  build-wheels:
    name: Build wheels on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-20.04
          - windows-2019
          - macos-13
          - macos-14
    steps:
      - uses: actions/checkout@v4

      ##################################################
      # Linux only: get QEMU, required by cibuildwheel
      ##################################################
      - name: Set up QEMU
        if: runner.os == 'Linux'
        uses: docker/setup-qemu-action@v2
        with:
          platforms: all

      ##################################################
      # Non-Linux: build wandb-core
      #
      # See comment above CIBW_BEFORE_ALL_LINUX.
      ##################################################
      - name: Set up Go
        if: runner.os != 'Linux'
        uses: actions/setup-go@v5
        with:
          go-version-file: core/go.mod
          cache-dependency-path: core/go.sum

      - name: Set up Python
        if: runner.os != 'Linux'
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - name: Install requirements for wini
        if: runner.os != 'Linux'
        run: pip install -r requirements_tools.txt

      - name: Build artifacts using wini (Windows and macOS-14)
        if: runner.os == 'Windows' || matrix.os == 'macOS-14'
        run: bash wini build wandb-core-artifacts

        # We skip building AppleStats on macOS-13 because it fails
        # cibuildwheel's repair step in delocation. This seems to be because
        # the Swift standard libraries aren't stored in /usr/lib/swift/ for
        # x86_64 architecture.
        #
        # You can test this by compiling AppleStats for arm64 and x86_64
        # separately, and then running "otool -L AppleStats". On x86_64,
        # you'll see entries like "@rpath/libswiftCore.dylib", whereas on
        # arm64 they are "/usr/lib/swift/libswiftCore.dylib".
        #
        # This could just be a Swift compiler bug.
        #
        # Interestingly, "/usr/lib/swift/libswiftCore.dylib" doesn't exist
        # under /usr/lib/swift/ on my machine, yet the binary works.
        #
        # The "delocate" step copies non-system dependencies into the wheel.
        # It explicitly ignores "/usr/lib/..." paths, but when it sees
        # "@rpath/libswiftCore.dylib" it tries to find the binary. The RPATH
        # on x86_64 includes "/usr/lib/swift/", but like I mentioned, the
        # files don't actually exist, and the step fails.
      - name: Build artifacts using wini (macOS-13)
        if: matrix.os == 'macOS-13'
        run: bash wini build wandb-core-artifacts --skip-swift-build

      ##################################################
      # All platforms: create wheels
      ##################################################
      - name: Build wheels
        uses: pypa/cibuildwheel@v2.16.5
        with:
           package-dir: core
           output-dir: core/wheelhouse
        env:
          # In cp36-*, the wheel name sometimes includes additional dashes that
          # make it invalid, breaking the job.
          #
          # Not sure why we skip PyPy and musllinux builds.
          #
          # See https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip
          CIBW_SKIP: cp36-* pp* *musllinux*
          CIBW_ARCHS_LINUX: x86_64 aarch64
          # Linux is special because it's built in a container using qemu, so we must
          # build our Go binary inside the cibuildwheel step.
          #
          # Some downsides:
          #   - We have to download Go manually
          #   - Ugly `uname -m` output remapping
          #   - Ugly hack to read Go version from go.mod
          #   - We don't get caching
          CIBW_BEFORE_ALL_LINUX: >
            case $(uname -m) in
              aarch64) export DOWNLOAD_GOARCH=arm64;;
              x86_64) export DOWNLOAD_GOARCH=amd64;;
            esac &&
            export DOWNLOAD_GOVERSION=$( grep '^go' core/go.mod | cut -d' ' -f2 ) &&
            curl -L https://golang.org/dl/go$DOWNLOAD_GOVERSION.linux-$DOWNLOAD_GOARCH.tar.gz > go.tar.gz &&
            tar -C /usr/local/ -xzf go.tar.gz &&
            export PATH=$PATH:/usr/local/go/bin &&
            pip install -r requirements_tools.txt &&
            bash wini build wandb-core-artifacts

      - uses: actions/upload-artifact@v3
        with:
          name: wandb-core-distributions
          path: ./core/wheelhouse/*.whl


  test-pypi-publish:
    name: Publish to TestPyPI
    needs: build-wheels
    continue-on-error: true
    runs-on: ubuntu-latest
    environment:
      name: release
      url: https://test.pypi.org/p/wandb-core
    permissions:
      id-token: write  # trusted publishing
    steps:
      - name: Download all the wheels
        uses: actions/download-artifact@v3
        with:
          name: wandb-core-distributions
          path: dist/
      - name: List wheels
        run: ls dist/
      - name: Publish distribution to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: https://test.pypi.org/legacy/

  verify-test-pypi:
    name: Verify TestPyPI upload
    needs: test-pypi-publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install wandb-core from TestPyPI
        run: |
          python -m pip install --upgrade pip
          if [ "${{ inputs.use_pre }}" == "true" ]; then
            python -m pip install --index-url https://test.pypi.org/simple/ --pre wandb-core
          else
            python -m pip install --index-url https://test.pypi.org/simple/ wandb-core
          fi
      - name: Install wandb from source
        run: |
          python -m pip install .
      - name: Smoke-test wandb-core TestPyPI install against wandb@main
        run: |
          python -c "import wandb; run = wandb.init(settings={'mode': 'offline'}); run.finish()"
      - name: Install latest wandb from PyPI
        run: |
          python -m pip uninstall wandb -y
          python -m pip install --upgrade wandb
      - name: Smoke-test wandb-core TestPyPI install against latest wandb from PyPI
        run: |
          python -c "import wandb; run = wandb.init(settings={'mode': 'offline'}); run.finish()"

  pypi-publish:
    name: Publish to PyPI
    needs: test-pypi-publish
    runs-on: ubuntu-latest
    environment:
      name: release
      url: https://pypi.org/p/wandb-core
    permissions:
      id-token: write  # trusted publishing
    steps:
      - name: Download all the wheels
        uses: actions/download-artifact@v3
        with:
          name: wandb-core-distributions
          path: dist/
      - name: List wheels
        run: ls dist/
      - name: Publish distribution to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

  slack:
    name: Post to Slack
    needs: pypi-publish
    runs-on: ubuntu-latest
    steps:
      - name: Install from PyPI and get version
        id: get_version
        run: |
          sleep 60
          python -m pip install --upgrade pip
          if [ "${{ inputs.use_pre }}" == "true" ]; then
            python -m pip install --pre wandb-core
          else
            python -m pip install wandb-core
          fi
          echo "WANDB_CORE_VERSION=$(python -c 'import wandb_core; print(wandb_core.__version__)')" >> "$GITHUB_ENV"
      - name: Post to Slack
        id: slack
        uses: slackapi/slack-github-action@v1.24.0
        with:
          # Slack channel id, channel name, or user id to post message.
          # See also: https://api.slack.com/methods/chat.postMessage#channels
          # You can pass in multiple channels to post to by providing a comma-delimited list of channel IDs.
          channel-id: ${{ secrets.SLACK_SDK_RELEASE_CHANNEL_ID }}
          # For posting a simple plain text message
          slack-message: "W&B SDK Core (`wandb-core`) ${{ env.WANDB_CORE_VERSION }} released :tada:: https://pypi.org/project/wandb-core/${{ env.WANDB_CORE_VERSION }}/"
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
